package com.yqr.oss;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.*;
import com.yqr.common.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Decoder;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 阿里云存储，工具封装类
 *
 * @author tim
 */
@Slf4j
public class AliOSSKit {
    /**
     * http协议分隔符
     */
    private static String PROTOCOL_SEPARATOR = "://";
    /**
     * URL参数第一个分隔符
     */
    private static String FIRST_PARAMETER_SEPARATOR = "?";

    /**
     * 阿里当前bucket的url前缀
     */
    private static String ALI_OSS_SHOW_URL_PREFIX = "https://" + OSSSTSKit.BUCKET + "." + OSSSTSKit.INTERNAL_ENDPOINT;

    private static String BUCKET = OSSSTSKit.BUCKET;

    private static OSSClient client;

    /**
     * 初始化 client
     *
     * @return
     */
    private static OSSClient initOSSClient() {
        if (null == client) {
            client = new OSSClient(OSSSTSKit.INTERNAL_ENDPOINT, OSSSTSKit.STS_ACCESS_KEY_ID, OSSSTSKit.STS_ACCESS_KEY_SECRET);
        }
        return client;
    }

    /**
     * 判断OOS对象是否存在
     *
     * @return boolean
     * @author zhuyongsheng
     * @date 2019-11-27
     */
    public static boolean doesObjectExist(String osskey) {
        OSSClient client = initOSSClient();
        return client.doesObjectExist(BUCKET, osskey);
    }

    /**
     * base64 上传文件
     *
     * @param base64Str base64 加密字符串
     * @param pathName  文件的路径(从跟目录开始)+名字，例如“file/xxxx.jpg” ,图片路径禁止以 / 开头
     * @return
     * @throws OSSException
     * @throws ClientException
     */
    public static boolean uploadFile(String base64Str, String pathName) {
        try {
            OSSClient client = initOSSClient();
            BASE64Decoder decoder = new BASE64Decoder();
            byte[] bytes = decoder.decodeBuffer(base64Str);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
            client.putObject(BUCKET, pathName, byteArrayInputStream);
        } catch (Exception e) {
            log.error("阿里云 base64 上传图片出错", e);
            return false;
        }
        return true;
    }

    /**
     * 上传文件
     *
     * @param mFile    MultipartFile对象
     * @param pathName 文件的路径(从跟目录开始)+名字，例如“file/xxxx.jpg” ,图片路径禁止以 / 开头
     * @return
     * @throws OSSException
     * @throws ClientException
     */
    public static boolean uploadFile(MultipartFile mFile, String pathName)
            throws OSSException, ClientException {
        InputStream input = null;
        try {
            input = mFile.getInputStream();
            uploadFile(input, pathName, mFile.getContentType(), mFile.getSize());
        } catch (IOException e) {
            return false;
        } finally {
            IOUtils.closeQuietly(input);
        }
        return true;
    }

    /**
     * 上传文件，通过流的方式
     *
     * @param is
     * @param pathName    文件的路径(从跟目录开始)+名字，例如“file/xxxx.jpg” ,图片路径禁止以 / 开头
     * @param contentType 文件的内容类型
     * @param fileSize
     * @return
     */
    public static boolean uploadFile(InputStream is, String pathName, String contentType, long fileSize) {
        if (is == null) {
            return false;
        }
        is = new BufferedInputStream(is);
        // 拼接头信息
        ObjectMetadata objectMeta = new ObjectMetadata();
        objectMeta.setContentLength(fileSize);

        // 可以在metadata中标记文件类型
        objectMeta.setContentType(contentType);

        OSSClient client = initOSSClient();
        client.putObject(BUCKET, pathName, is, objectMeta);
        return true;
    }

    /**
     * 上传文件，通过流的方式
     *
     * @param pathName 文件的路径(从跟目录开始)+名字，例如“file/xxxx.jpg” ,图片路径禁止以 / 开头
     * @param is
     * @return
     */
    public static boolean uploadFile(String pathName, InputStream is) {
        if (is == null) {
            return false;
        }
        is = new BufferedInputStream(is);

        OSSClient client = initOSSClient();
        client.putObject(BUCKET, pathName, is);
        return true;
    }

    /**
     * 上传文件，通过本地文件的方式
     *
     * @param file
     * @param pathName 文件的路径(从跟目录开始)+名字，例如“file/xxxx.jpg” ,图片路径禁止以 / 开头
     * @return
     * @throws OSSException
     * @throws ClientException
     */
    public static boolean uploadFile(File file, String pathName)
            throws OSSException, ClientException {
        OSSClient client = initOSSClient();
        client.putObject(BUCKET, pathName, file);
        return true;
    }

    /**
     * @param objectName 对象名
     * @param dstFile    要下载到的目标文件
     * @return
     */
    public static boolean downloadFile(String bucket, String objectName, String dstFile) {
        OSSClient ossClient = initOSSClient();
        // 下载OSS文件到本地文件。如果指定的本地文件存在会覆盖，不存在则新建。
        ossClient.getObject(new GetObjectRequest(bucket, objectName), new File(dstFile));
        return true;
    }

    /**
     * @param objectName 对象名
     * @param dstFile    要下载到的目标文件
     * @param style      图片样式
     * @return
     */
    public static boolean downloadFile(String bucket, String objectName, String dstFile, String style) {
        OSSClient ossClient = initOSSClient();
        // 下载OSS文件到本地文件。如果指定的本地文件存在会覆盖，不存在则新建。
        GetObjectRequest getObjectRequest = new GetObjectRequest(bucket, objectName);
        getObjectRequest.setProcess(style);
        ossClient.getObject(getObjectRequest, new File(dstFile));
        return true;
    }

    /**
     * 下载OSS文件到本地文件。如果指定的本地文件存在会覆盖，不存在则新建。
     *
     * @param url     形如`https://bucket.domain.com/object/full/name.xxx`，保存在oss上的文件。
     *                将会将object/full/name.xxx作为objectName
     * @param dstFile 下载到哪个文件
     */
    public static void downloadFileFromUrl(String url, String dstFile) {
        //处理url为空的情况
        if (url == null || url.isEmpty()) {
            throw new ServiceException("下载文件失败，url为空");
        }

        //移除协议头
        url = url.replaceAll("^(https|http)://", "");
        url = url.replaceAll("//", "");

        //url中不包含object名
        if (!url.contains("/")) {
            throw new ServiceException("下载文件失败，url格式错误");
        }

        //String bucket = url.substring(0, url.indexOf("."));//获取bucket

        String objectName = url.substring(url.indexOf("/") + 1);
        String style = "";
        if (objectName.contains("?")) {
            style = objectName.substring(objectName.indexOf("?") + 1)
                    .replace("x-oss-process=", "")
            ;
            objectName = objectName.substring(0, objectName.indexOf("?"));

            downloadFile(BUCKET, objectName, dstFile, style);
        } else {
            downloadFile(BUCKET, objectName, dstFile);
        }
    }

    /**
     * 设置文件或者目录的访问权限
     *
     * @param pathName 要设置的对象
     * @param access   权限控制类型，例如：CannedAccessControlList.PublicRead
     * @return
     */
    public static boolean setAcl(String pathName, CannedAccessControlList access) {
        OSSClient client = initOSSClient();
        client.setObjectAcl(BUCKET, pathName, access);
        return true;
    }

    /**
     * 把相对地址转成绝对地址，并返回
     *
     * @param relativeUrl
     * @return
     */
    public static String getAbsoluteUrl(String relativeUrl) {
        if (StringUtils.isBlank(relativeUrl) || isAbsoluteUrl(relativeUrl)) {
            return relativeUrl;
        }
        if (!relativeUrl.startsWith("/")) {
            relativeUrl = "/" + relativeUrl;
        }
        String rst = relativeUrl;
        if (!relativeUrl.contains(ALI_OSS_SHOW_URL_PREFIX)) {
            rst = ALI_OSS_SHOW_URL_PREFIX + relativeUrl;
        }
        return rst;
    }

    /**
     * 多个地址拼接的把相对地址转成绝对地址，并返回
     *
     * @param relativeUrl
     * @param split       隔开符号
     * @return
     */
    public static String getAbsoluteUrl(String relativeUrl, String split) {
        String rst = relativeUrl;
        if (StringUtils.isNotBlank(relativeUrl)) {
            rst = ALI_OSS_SHOW_URL_PREFIX + relativeUrl.replace(split, "," + ALI_OSS_SHOW_URL_PREFIX);
        }
        return rst;
    }

    private static boolean isAbsoluteUrl(String relativeUrl) {
        return relativeUrl.startsWith("http") || relativeUrl.startsWith("//");
    }

    /**
     * 删除存储在阿里云上面的文件
     *
     * @param pathName 文件的路径(从根目录开始)如gameCoverPic/xxx.jpg
     */
    public static void delFile(String pathName) {
        if (StringUtils.isNotBlank(pathName)) {
            OSSClient client = initOSSClient();
            client.deleteObject(BUCKET, pathName);
        }
    }

    /**
     * 根据前缀匹配删除oss文件
     *
     * @param prefix 前缀
     * @author tim
     * @date 2020/6/4 12:27
     */
    public static void delFilesWithPrefix(String prefix) {
        if (StringUtils.isEmpty(prefix)) {
            log.error("oss prefix is null");
            return;
        }
        initOSSClient();
        // 列举所有包含指定前缀的文件并删除。
        String nextMarker = null;
        ObjectListing objectListing;
        do {
            ListObjectsRequest listObjectsRequest = new ListObjectsRequest(BUCKET)
                    .withPrefix(prefix)
                    .withMarker(nextMarker);

            objectListing = client.listObjects(listObjectsRequest);
            if (CollectionUtils.isNotEmpty(objectListing.getObjectSummaries())) {
                List<String> keys = new ArrayList<>();
                for (OSSObjectSummary s : objectListing.getObjectSummaries()) {
                    log.info("del key name: {}", s.getKey());
                    keys.add(s.getKey());
                }
                DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(BUCKET).withKeys(keys);
                client.deleteObjects(deleteObjectsRequest);
            }

            nextMarker = objectListing.getNextMarker();
        } while (objectListing.isTruncated());
    }

    /**
     * 缩放图片
     * 固定宽高，缩略填充
     *
     * @param imageUrl 图片路径
     * @param width    宽度
     * @param height   高度
     * @return
     */
    public static String resizePadImage(String imageUrl, int width, int height) {
        return imageUrl + "?x-oss-process=image/resize,m_pad,h_" + height + ",w_" + width;
    }

    /**
     * 缩放图片
     * 等比缩放，限定在矩形框内，将图缩略成宽度为 width，高度为 hieght，按长边优先
     *
     * @param imageUrl 图片路径
     * @param width    宽度
     * @param height   高度
     * @return
     */
    public static String resizeLfitImage(String imageUrl, int width, int height) {
        return imageUrl + "?x-oss-process=image/resize,m_lfit,h_" + height + ",w_" + width;
    }

    /**
     * 裁剪图片
     * 居中裁剪指定尺寸
     *
     * @param imageUrl 图片路径
     * @param width    宽度
     * @param height   高度
     * @return
     */
    public static String cropImage(String imageUrl, int width, int height) {
        return imageUrl + "?x-oss-process=image/crop,x_0,y_0,h_" + height + ",w_" + width + ",g_center";
    }

    /**
     * 裁剪图片
     * 设定图片质量比例
     *
     * @param imageUrl 图片路径
     * @param quality  质量百分数
     * @return
     */
    public static String setImageQuality(String imageUrl, int quality) {
        if (StringUtils.isNotBlank(imageUrl)) {
            return imageUrl + "?x-oss-process=image/quality,q_" + quality;
        } else {
            return imageUrl;
        }
    }

    /**
     * 裁剪图片 + 绝对路径
     * 设定图片质量比例
     *
     * @param imageUrl 图片路径
     * @param quality  质量百分数
     * @return
     */
    public static String setImageQualityUrl(String imageUrl, int quality) {
        String url = getAbsoluteUrl(imageUrl);
        if (StringUtils.isNotBlank(url)) {
            return url + "?x-oss-process=image/quality,q_" + quality;
        } else {
            return url;
        }
    }

    /**
     * copy 图片从临时目录到真正目录
     *
     * @param imageUrl              图片地址
     * @param destinationObjectName 目标文件名称
     * @return String
     * @author tim
     * @date 2021/8/14 14:39
     */
    public static String copyImageRequest(String imageUrl, String destinationObjectName) {
        OSSClient client = null;
        String returnUrl = null;
        try {
            client = initOSSClient();
            String sourceObjectName = imageUrl.substring(imageUrl.indexOf(".com") + 5, imageUrl.lastIndexOf(".") + 4);
            String imageParam = imageUrl.substring(imageUrl.lastIndexOf(".") + 4);
            // 拷贝文件。
            CopyObjectResult result = client.copyObject(BUCKET, sourceObjectName, BUCKET, destinationObjectName);
            log.info("ETag:{}, LastModified:{}", result.getETag(), result.getLastModified());
            returnUrl = "https://" + OSSSTSKit.BUCKET + "." + OSSSTSKit.ENDPOINT + "/" + destinationObjectName + imageParam;
        } catch (Exception e) {
            log.error("阿里云 copy 图片出错", e);
        }
        return returnUrl;
    }

    /**
     * oss文件复制，在原有文件名附加名称
     *
     * @param url       源文件http地址
     * @param affixName 附加的名称
     * @return String
     * @author tim
     * @date 2021/4/5
     */
    public static String copyFile(String url, String affixName) {
        String returnUrl = null;
        try {
            OSSClient client = initOSSClient();
            if (StringUtils.isNotBlank(url) && url.contains(PROTOCOL_SEPARATOR)) {
                //去除oss压缩参数
                if (url.contains(FIRST_PARAMETER_SEPARATOR)) {
                    url = url.substring(0, url.lastIndexOf(FIRST_PARAMETER_SEPARATOR));
                    log.debug("origin file url={}", url);
                }
                String[] split = url.split(PROTOCOL_SEPARATOR);
                String protocol = split[0];
                String hostNameAndPathStr = split[1];
                log.debug("protocol={} hostNameAndPathStr={}", protocol, hostNameAndPathStr);
                String sourceObjectName = hostNameAndPathStr.substring(hostNameAndPathStr.indexOf("/") + 1);
                log.debug("sourceObjectName={}", sourceObjectName);

                String baseName = FilenameUtils.getBaseName(url);
                String destinationObjectName = sourceObjectName.replace(baseName, baseName + affixName);
                log.debug("destinationObjectName={}", destinationObjectName);

                // 拷贝文件
                CopyObjectResult result = client.copyObject(BUCKET, sourceObjectName, BUCKET, destinationObjectName);
                log.debug("ETag:{}, LastModified:{}", result.getETag(), result.getLastModified());

                returnUrl = url.replace(baseName, baseName + affixName);
                log.debug("returnUrl={}", returnUrl);
            }
        } catch (Exception e) {
            log.error("oss复制文件出现异常", e);
        }
        return returnUrl;
    }

    /**
     * 删除oss文件
     *
     * @param url 文件http地址
     */
    public static void deleteByFileUrl(String url) {
        try {
            if (StringUtils.isNotBlank(url) && url.contains(PROTOCOL_SEPARATOR)) {
                //去除oss压缩参数
                if (url.contains(FIRST_PARAMETER_SEPARATOR)) {
                    url = url.substring(0, url.lastIndexOf(FIRST_PARAMETER_SEPARATOR));
                    log.debug("origin file url={}", url);
                }
                String[] split = url.split(PROTOCOL_SEPARATOR);
                String protocol = split[0];
                String hostNameAndPathStr = split[1];
                log.debug("protocol={} hostNameAndPathStr={}", protocol, hostNameAndPathStr);
                //获取文件的ObjectName (从根目录开始)如 yiqian_ticket_pic/1833/2020-8-4/xxx.jpg
                String sourceObjectName = hostNameAndPathStr.substring(hostNameAndPathStr.indexOf("/") + 1);
                log.debug("sourceObjectName={}", sourceObjectName);

                OSSClient client = initOSSClient();
                client.deleteObject(BUCKET, sourceObjectName);
            }
        } catch (Exception e) {
            log.error("oss删除文件出现异常", e);
        }
    }

}
